Двусмислие
Двусмислие може да възникне във връзка с
неявното извикване на оператори конвертори. Например,
extern void f( int
);
extern void f( SmallInt );
Token tok( "string constant", 3 );
f( tok
); // error: ambiguous
Token дефинира оператор конвертор както за
SmallInt така и за int: двете преобразувания са еднакво възможни. Обръщението
f(tok) е двусмислено, понеже компилаторът не може да избере измежду двата
конвертора. Затова обръщението предизвиква грешка по време на компилация.
Програмистът може да разреши двусмислието като направи преобразуването
явно:
// explicit convertion resolve ambiguityf( int(tok) );
Ако
Token::Operator int() не е дефиниран, обръщението няма да бъде двусмислено. Ще
бъде изпълнен Token::Operator SmallInt(). Фактът, че SmallInt дефинира оператор
конвертор int не се разглежда. При определяне на възможните преобразувания на
един обект, се преглежда само първото ниво на операторите конвертори, дефинирани
от потребителя.
Ако е възможно прилагането на два оператора за
преобразуване, но при единия има точно съответствие, а другият изисква
стандартно преобразуване, няма двусмислие - избира се оператора конвертор с
точното съответствие. Например,
class Token{public:
operator
float();
operator int();
...};
Обаче, ако и двата оператора
конвертора са еднакво приложими, те се отбелязват като двусмислени:
//
error: both operators float() and int()long lval = tok;
Двусмислие може
да възникне и когато два класа дефинират преобразувание помежду си.
Например,
SmallInt::SmallInt( Token& );
Token::Operator
SmallInt();
extern void f( SmallInt );
Token tok( "pointer to class
member", 197 );// error: two possible user-defined conversionsf( tok );
В
този случай съществуват два еднакво възможни начина за преобразуване на tok в
обект на SmallInt. Обръщението е двусмислено понеже няма начин компилаторът да
направи избор между двата начина; обръщението се отбелязва като грешка по време
на компилация. Програмистът може да разреши двусмислието като направи
преобразуването явно. Например,
// explicit convertion resolves
ambiguity
// Token::Operator SmallInt() invoked
f( (SmallInt)tok
);
Програмистът може да разреши двусмислието и чрез явно извикване на
оператора конвертор на класа Token:
// explicit conversion resolves
ambiguity
f( tok.operator SmallInt() ):
Забележете, че явното
преобразуване в записа на функцията все още е двусмислено.
// error:
still ambiguous
f( SmallInt(tok) );
SmallInt(Token&) и Token::Operator
SmallInt() са еднакво възможни интерпретации на обръщението.
Една втора
форма на двусмисилие се свързва с читателя на програма, която използува
оператори конвертори. Когато един обект на SmallInt се преобразува до стойност
от тип int, значението на това преобразувание е ясно. Когато един обект на
String се преобразува до типа char*, значението на това преобразувание също е
ясно. И в двата случая съществува съответствие едно-към-едно между вградените
типове данни и вътрешното представяне на типа на класа. Когато, обаче няма
логическо съответствие между оператора конвертор и типа на класа, използуването
на обектите на класа може да стане двусмислено за читателя на програмата.
Например,
class Date {
public:
// guess which member is
returned!
operator int();
private:
Int month, day, year;};
Каква
стойност трябва да бъде върната от оператора конвертор int на Date? Какъвто и
избор да бъде направен по каквато и да било добра причина, използуването на
обекти на Date ще бъде двусмислено за читателя на програмата понеже няма логично
съответствие едно-към-едно. В този случай би било много по-добре да не се
дефинира оператор за конвертиране.
Упражнение 7-28. Какви оператори
конвертори са необходими за поддържане на следното използуване на обекти на
класа String?
extern int strlen( const char* );
const maxLen =
32;
String st = "a string";
int len = strlen( st );
if ( st >=
maxLen ) ... String st2 = maxLen;
Упражнение 7-29. Разгледайте
предимствата и недостатъците от поддържането оператора конвертор, представен
чрез
if ( st >= maxLen ) ...
Упражнение 7-30. Един интересен
оператор конвертор за BinTree връща обект на класа IntArray от стойности на
INode (Раздел 2.8 съдържа дефиницията на класа IntArray). Реализирайте следните
два оператора конвертора:
BinTree::BinTree( IntArray&
);
BinTree::Operator IntArray();
Забележете, че реда за преглед на
дървото трябва да бъде еднакъв и за двата оператора. Може да бъде установен един
предварително установен ред за преглед със следната обща
форма:
BinTree::preorder( /* ??? */ ) {
if ( !node )
return;
node->visit( /* ??? *? ); // do work
if ( left )
left->preOrder( /* ??? */ );
if ( right ) right->preOrder( /* ??? */
);
Упражнение 7-31. Предишното упражнение предлага обработване на изрази,
включващи обекти от двата класа IntArray и BinTree. Например,
extern
BinTree bt;
extern IntArray ia;
if ( ia == bt ) ...
Редът на
операциите е следният: Извиква се оператора конвертор на BinTree за да
преобразува bt в обект на класа IntArray. Тогава се извиква оператора за
равенство на IntArray за да сравни двата обекта на класа IntArray. Какво ще се
случи, обаче, ако класа IntArray дефинира следните два оператора
конвертори?
IntArray::IntArray( BinTree& );
IntArray::Operator
BinTree();